// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'dart:math' as math;

import 'package:charts_grasp/src/chart/axis/axis.dart';
import 'package:charts_grasp/src/chart/axis/category_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_category_axis.dart';
import 'package:charts_grasp/src/chart/axis/logarithmic_axis.dart';
import 'package:charts_grasp/src/chart/axis/numeric_axis.dart';
import 'package:charts_grasp/src/chart/base/chart_base.dart';
import 'package:charts_grasp/src/chart/chart_series/series.dart';
import 'package:charts_grasp/src/chart/chart_series/series_renderer_properties.dart';
import 'package:charts_grasp/src/chart/chart_series/xy_data_series.dart';
import 'package:charts_grasp/src/chart/common/cartesian_state_properties.dart';
import 'package:charts_grasp/src/chart/technical_indicators/accumulation_distribution_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/atr_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/bollinger_bands_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/ema_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/macd_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/momentum_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/rsi_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/sma_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/stochastic_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/technical_indicator.dart';
import 'package:charts_grasp/src/chart/technical_indicators/tma_indicator.dart';
import 'package:charts_grasp/src/chart/trendlines/trendlines.dart';
import 'package:charts_grasp/src/chart/utils/enum.dart';
import 'package:charts_grasp/src/chart/utils/helper.dart';
import 'package:flutter/material.dart';

class ChartSeriesPanel {
  ChartSeriesPanel(this.stateProperties);

  final CartesianStateProperties stateProperties;

  CgCartesianChart get chart => stateProperties.chart;

  bool isStacked100 = false;

  int paletteIndex = 0;

  num sumOfYvalues = 0;

  List<num> yValues = <num>[];

  List<CartesianSeriesRenderer> visibleSeriesRenderers = <CartesianSeriesRenderer>[];

  bool needAxisRangeAnimation = false;

  void processData() {
    final List<CartesianSeriesRenderer> seriesRendererList = visibleSeriesRenderers;
    isStacked100 = false;
    paletteIndex = 0;
    _findAreaType(seriesRendererList);
    if (chart.indicators.isNotEmpty) {
      _populateDataPoints(seriesRendererList);
      _calculateIndicators();
      stateProperties.chartAxis.calculateVisibleAxes();
      _findMinMax(seriesRendererList);
      _renderTrendline();
    } else {
      stateProperties.chartAxis.calculateVisibleAxes();
      _populateDataPoints(seriesRendererList);
    }
    _renderTrendline();
  }

  bool _needAxisAnimation(CartesianSeriesRenderer seriesRenderer, CartesianSeriesRenderer oldSeriesRenderer) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
    final SeriesRendererDetails oldSeriesRendererDetails = SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer);
    final dynamic oldAxis = oldSeriesRendererDetails.xAxisDetails!.axis;
    final dynamic axis = seriesRendererDetails.xAxisDetails!.axis;
    final bool needAnimation = seriesRendererDetails.series.animationDuration > 0 == true &&
        seriesRendererDetails.yAxisDetails!.runtimeType == oldSeriesRendererDetails.yAxisDetails!.runtimeType &&
        seriesRendererDetails.xAxisDetails!.runtimeType == oldSeriesRendererDetails.xAxisDetails!.runtimeType &&
        ((oldAxis.visibleMinimum != null && oldAxis.visibleMinimum != axis.visibleMinimum) ||
            (oldAxis.visibleMaximum != null && oldAxis.visibleMaximum != axis.visibleMaximum));
    needAxisRangeAnimation = needAnimation;
    return needAnimation;
  }

  void _populateDataPoints(List<CartesianSeriesRenderer> seriesRendererList) {
    stateProperties.totalAnimatingSeries = 0;
    bool isSelectionRangeChangeByEvent = false;
    for (final CartesianSeriesRenderer seriesRenderer in seriesRendererList) {
      final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
      final CartesianSeries<dynamic, dynamic> series = seriesRendererDetails.series;
      seriesRendererDetails.minimumX = seriesRendererDetails.minimumY =
          seriesRendererDetails.minDelta = seriesRendererDetails.maximumX = seriesRendererDetails.maximumY = null;
      seriesRendererDetails.needAnimateSeriesElements = false;
      seriesRendererDetails.needsAnimation = false;
      seriesRendererDetails.reAnimate = false;
      CartesianChartPoint<dynamic>? currentPoint;
      yValues = <num>[];
      sumOfYvalues = 0;
      seriesRendererDetails.dataPoints = <CartesianChartPoint<dynamic>>[];
      seriesRendererDetails.xValues = <dynamic>[];
      if (!isStacked100 && seriesRendererDetails.seriesType.contains('100') == true) {
        isStacked100 = true;
      }
      if (seriesRendererDetails.visible! == true) {
        stateProperties.totalAnimatingSeries++;
      }
      final String seriesType = seriesRendererDetails.seriesType;
      final bool needSorting = series.sortingOrder != SortingOrder.none && series.sortFieldValueMapper != null;
      dynamic previousX;
      dynamic currentX;
      dynamic nextX;
      dynamic yVal;
      num? low;
      num? high;
      seriesRendererDetails.overAllDataPoints = <CartesianChartPoint<dynamic>?>[];
      CartesianChartPoint<dynamic>? nextPoint;
      CartesianChartPoint<dynamic>? prevPoint;
      for (int pointIndex = 0; pointIndex < series.dataSource.length;) {
        currentPoint = getChartPoint(seriesRenderer, series.dataSource[pointIndex], pointIndex);
        if (pointIndex < series.dataSource.length - 1 && seriesRendererDetails.seriesType != 'histogram') {
          nextPoint = getChartPoint(seriesRenderer, series.dataSource[pointIndex + 1], pointIndex + 1);
        } else {
          nextPoint = currentPoint;
        }
        currentX = currentPoint?.x;
        nextX = nextPoint?.x;
        previousX = pointIndex == 0 ? currentPoint?.x : prevPoint?.x;
        yVal = currentPoint?.y;
        high = currentPoint?.high;
        low = currentPoint?.low;
        currentPoint?.overallDataPointIndex = pointIndex;

        seriesRendererDetails.overAllDataPoints.add(currentPoint);

        if (currentX != null) {
          final dynamic xAxis = seriesRendererDetails.xAxisDetails?.axis;
          final dynamic yAxis = seriesRendererDetails.yAxisDetails?.axis;
          dynamic xMin = xAxis?.visibleMinimum;
          dynamic xMax = xAxis?.visibleMaximum;
          final dynamic yMin = yAxis?.visibleMinimum;
          final dynamic yMax = yAxis?.visibleMaximum;
          dynamic xPointValue = currentX;
          bool isXVisibleRange = true;
          bool isYVisibleRange = true;
          if (xAxis is DateTimeAxis) {
            xMin = xMin != null ? xMin.millisecondsSinceEpoch : xMin;
            xMax = xMax != null ? xMax.millisecondsSinceEpoch : xMax;
            xPointValue = xPointValue?.millisecondsSinceEpoch;
            nextX = nextX?.millisecondsSinceEpoch;
            previousX = previousX?.millisecondsSinceEpoch;
          } else if (xAxis is CategoryAxis) {
            xPointValue = pointIndex;
            nextX = pointIndex + 1;
            previousX = pointIndex - 1;
          } else if (xAxis is DateTimeCategoryAxis) {
            xMin = xMin != null ? xMin.millisecondsSinceEpoch : xMin;
            xMax = xMax != null ? xMax.millisecondsSinceEpoch : xMax;
            xPointValue = xPointValue?.millisecondsSinceEpoch;
            nextX = nextX?.millisecondsSinceEpoch;
            previousX = previousX?.millisecondsSinceEpoch;
          }
          if (xMin != null || xMax != null) {
            isXVisibleRange = false;
          }
          if (yMin != null || yMax != null) {
            isYVisibleRange = false;
          }

          if ((xMin != null || xMax != null || yMin != null || yMax != null) &&
              stateProperties.oldSeriesRenderers.isNotEmpty) {
            final int seriesIndex = stateProperties.chartSeries.visibleSeriesRenderers.indexOf(seriesRenderer);
            final CartesianSeriesRenderer? oldSeriesRenderer =
                stateProperties.oldSeriesRenderers.length - 1 >= seriesIndex
                    ? stateProperties.oldSeriesRenderers[seriesIndex]
                    : null;
            if (oldSeriesRenderer != null &&
                (stateProperties.chart.onSelectionChanged != null ||
                    _needAxisAnimation(seriesRenderer, oldSeriesRenderer))) {
              final SeriesRendererDetails oldSeriesRendererDetails =
                  SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer);
              isSelectionRangeChangeByEvent = oldSeriesRendererDetails.minimumX != xMin ||
                  oldSeriesRendererDetails.maximumX != xMax ||
                  oldSeriesRendererDetails.minimumY != yMin ||
                  oldSeriesRendererDetails.maximumY != yMax;
            }
          }

          if (!(!(isSelectionRangeChangeByEvent ||
                      stateProperties.rangeChangeBySlider ||
                      (stateProperties.zoomedState ?? false) ||
                      stateProperties.renderingDetails.didSizeChange ||
                      stateProperties.zoomProgress) &&
                  (xMin != null || xMax != null || yMin != null || yMax != null) &&
                  (yVal != null || (low != null && high != null))) ||
              ((xMin != null && xMax != null)
                      ? (xPointValue >= xMin) == true && (xPointValue <= xMax) == true
                      : xMin != null
                          ? xPointValue >= xMin
                          : xMax != null
                              ? xPointValue <= xMax
                              : false) ==
                  true ||
              ((yMin != null && yMax != null)
                      ? ((yVal ?? low) >= yMin) == true && ((yVal ?? high) <= yMax) == true
                      : yMin != null
                          ? (yVal ?? low) >= yMin
                          : yMax != null
                              ? (yVal ?? high) <= yMax
                              : false) ==
                  true ||
              ((xMin != null && xPointValue <= xMin && nextX > xMin) ||
                  ((xMin != null && xMax != null) &&
                      ((xPointValue <= xMin && nextX >= xMax) || (previousX <= xMin && xPointValue >= xMax))) ||
                  (xMax != null && (previousX < xMax && xPointValue >= xMax)))) {
            isXVisibleRange = true;
            isYVisibleRange = true;
            seriesRendererDetails.dataPoints.add(currentPoint!);
            seriesRendererDetails.xValues!.add(currentX);

            if (seriesType.contains('range') ||
                    seriesType.contains('hilo') ||
                    seriesType.contains('candle') ||
                    seriesType == 'boxandwhisker'
                ? seriesType == 'hiloopenclose' || seriesType.contains('candle')
                    ? (currentPoint.low == null ||
                        currentPoint.high == null ||
                        currentPoint.open == null ||
                        currentPoint.close == null)
                    : seriesType == 'boxandwhisker'
                        ? (currentPoint.minimum == null ||
                            currentPoint.maximum == null ||
                            currentPoint.lowerQuartile == null ||
                            currentPoint.upperQuartile == null)
                        : (currentPoint.low == null || currentPoint.high == null)
                : currentPoint.y == null) {
              if (seriesRenderer is XyDataSeriesRenderer && seriesType != 'waterfall') {
                seriesRenderer.calculateEmptyPointValue(pointIndex, currentPoint, seriesRenderer);
              }
            }

            if ((seriesType.contains('range') ||
                    seriesType.contains('hilo') ||
                    seriesType.contains('candle') ||
                    seriesType == 'boxandwhisker') &&
                currentPoint.isVisible) {
              if (seriesType == 'boxandwhisker') {
                final num max = currentPoint.maximum!;
                final num min = currentPoint.minimum!;
                currentPoint.maximum = math.max<num>(max, min);
                currentPoint.minimum = math.min<num>(max, min);
              } else {
                final num high = currentPoint.high;
                final num low = currentPoint.low;
                currentPoint.high = math.max<num>(high, low);
                currentPoint.low = math.min<num>(high, low);
              }
            }
            if (seriesRendererDetails.needsAnimation == false && needAxisRangeAnimation != true) {
              if (seriesRendererDetails.oldSeries == null ||
                  seriesRendererDetails.oldDataPoints!.length < seriesRendererDetails.dataPoints.length == true) {
                seriesRendererDetails.needAnimateSeriesElements = true;
                seriesRendererDetails.needsAnimation = seriesRendererDetails.visible!;
              } else {
                seriesRendererDetails.needsAnimation =
                    (seriesRendererDetails.dataPoints.length <= seriesRendererDetails.oldDataPoints!.length) == true
                        ? seriesRendererDetails.visible! == true &&
                            findChangesInPoint(
                              currentPoint,
                              seriesRendererDetails.oldDataPoints![seriesRendererDetails.dataPoints.length - 1],
                              seriesRendererDetails,
                            )
                        : seriesRendererDetails.visible!;
              }
            }
          }
          if (seriesRendererDetails.xAxisDetails != null &&
              seriesRendererDetails.yAxisDetails != null &&
              !needSorting &&
              chart.indicators.isEmpty) {
            _findMinMaxValue(seriesRendererDetails.xAxisDetails!.axisRenderer, seriesRenderer, currentPoint!,
                pointIndex, series.dataSource.length, isXVisibleRange, isYVisibleRange,);
          }
        }
        pointIndex = seriesRendererDetails.seriesType != 'histogram' ? pointIndex + 1 : pointIndex + yVal as int;
        prevPoint = currentPoint;
      }
      if (seriesRendererDetails.xAxisDetails is DateTimeCategoryAxisRenderer) {
        _sortDateTimeCategoryDetails(seriesRendererDetails);
      }
      if (needSorting) {
        _sortDataSource(seriesRendererDetails);
        if (chart.indicators.isEmpty) {
          findSeriesMinMax(seriesRendererDetails);
        }
      }
    }
  }

  void _findMinMaxValue(ChartAxisRenderer axisRenderer, CartesianSeriesRenderer seriesRenderer,
      CartesianChartPoint<dynamic> currentPoint, int pointIndex, int dataLength,
      [bool? isXVisibleRange, bool? isYVisibleRange,]) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
    if (seriesRendererDetails.visible! == true) {
      final ChartAxisRendererDetails axisDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
      if (axisDetails is NumericAxisDetails) {
        axisDetails.findAxisMinMaxValues(
            seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange,);
      } else if (axisDetails is CategoryAxisDetails) {
        axisDetails.findAxisMinMaxValues(
            seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange,);
      } else if (axisDetails is DateTimeAxisDetails) {
        axisDetails.findAxisMinMaxValues(
            seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange,);
      } else if (axisDetails is LogarithmicAxisDetails) {
        axisDetails.findAxisMinMaxValues(
            seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange,);
      } else if (axisDetails is DateTimeCategoryAxisDetails) {
        axisDetails.findAxisMinMaxValues(
            seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange,);
      }
    }
  }

  void findSeriesMinMax(SeriesRendererDetails seriesRendererDetails) {
    final ChartAxisRenderer axisRenderer = seriesRendererDetails.xAxisDetails!.axisRenderer;
    if (seriesRendererDetails.visible! == true) {
      for (int pointIndex = 0; pointIndex < seriesRendererDetails.dataPoints.length; pointIndex++) {
        _findMinMaxValue(axisRenderer, seriesRendererDetails.renderer, seriesRendererDetails.dataPoints[pointIndex],
            pointIndex, seriesRendererDetails.dataPoints.length, true, true,);
      }
    }
  }

  void _findMinMax(List<CartesianSeriesRenderer> seriesCollection) {
    SeriesRendererDetails seriesRendererDetails;
    for (int seriesIndex = 0; seriesIndex < seriesCollection.length; seriesIndex++) {
      seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesCollection[seriesIndex]);
      findSeriesMinMax(seriesRendererDetails);
    }
  }

  void _renderTrendline() {
    for (final CartesianSeriesRenderer seriesRenderer in visibleSeriesRenderers) {
      final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
      if (seriesRendererDetails.series.trendlines != null) {
        TrendlineRenderer trendlineRenderer;
        Trendline trendline;
        for (int i = 0; i < seriesRendererDetails.series.trendlines!.length; i++) {
          trendline = seriesRendererDetails.series.trendlines![i];
          trendlineRenderer = seriesRendererDetails.trendlineRenderer[i];
          trendlineRenderer.isNeedRender = trendlineRenderer.visible == true &&
              seriesRendererDetails.visible! == true &&
              (trendline.type == TrendlineType.polynomial
                  ? (trendline.polynomialOrder >= 2 && trendline.polynomialOrder <= 6)
                  : !(trendline.type == TrendlineType.movingAverage) ||
                      (trendline.period >= 2 &&
                          trendline.period <= seriesRendererDetails.series.dataSource.length - 1));
          trendlineRenderer.animationController = AnimationController(vsync: stateProperties.chartState)
            ..addListener(stateProperties.repaintTrendlines);
          stateProperties.controllerList[trendlineRenderer.animationController] = stateProperties.repaintTrendlines;
          if (trendlineRenderer.isNeedRender) {
            trendlineRenderer.setDataSource(seriesRendererDetails, chart);
          }
        }
      }
    }
  }

  void _sortDataSource(SeriesRendererDetails seriesRendererDetails) {
    seriesRendererDetails.dataPoints.sort(
        (firstPoint, secondPoint) {
      if (seriesRendererDetails.series.sortingOrder == SortingOrder.ascending) {
        return ((firstPoint.sortValue == null)
            ? -1
            : (secondPoint.sortValue == null
                ? 1
                : (firstPoint.sortValue is String
                    ? firstPoint.sortValue.toLowerCase().compareTo(secondPoint.sortValue.toLowerCase())
                    : firstPoint.sortValue.compareTo(secondPoint.sortValue)))) as int;
      } else if (seriesRendererDetails.series.sortingOrder == SortingOrder.descending) {
        return ((firstPoint.sortValue == null)
            ? 1
            : (secondPoint.sortValue == null
                ? -1
                : (firstPoint.sortValue is String
                    ? secondPoint.sortValue.toLowerCase().compareTo(firstPoint.sortValue.toLowerCase())
                    : secondPoint.sortValue.compareTo(firstPoint.sortValue)))) as int;
      } else {
        return 0;
      }
    });
  }

  void _findAreaType(List<CartesianSeriesRenderer> seriesRendererList) {
    if (visibleSeriesRenderers.isNotEmpty) {
      int index = -1;
      for (final CartesianSeriesRenderer series in seriesRendererList) {
        _setSeriesType(series, index += 1);
      }
    }
  }

  void _setSeriesType(CartesianSeriesRenderer seriesRenderer, int index) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
    if (seriesRendererDetails.series.color == null) {
      seriesRendererDetails.seriesColor = chart.palette[paletteIndex % chart.palette.length];
      paletteIndex++;
    } else {
      seriesRendererDetails.seriesColor = seriesRendererDetails.series.color;
    }

    seriesRendererDetails.seriesType = getSeriesType(seriesRenderer);

    if (index == 0) {
      final String seriesType = seriesRendererDetails.seriesType;
      stateProperties.requireInvertedAxis = chart.isTransposed ^
          ((seriesType.toLowerCase().contains('bar')) && (!seriesType.toLowerCase().contains('errorbar')));
    }
  }

  void _calculateIndicators() {
    if (chart.indicators.isNotEmpty) {
      dynamic indicator;
      bool existField;
      Map<String, int> map = <String, int>{};
      TechnicalIndicatorsRenderer technicalIndicatorsRenderer;
      if (!chart.legend.isVisible!) {
        final List<String> textCollection = <String>[];
        for (int i = 0; i < chart.indicators.length; i++) {
          final TechnicalIndicators<dynamic, dynamic> indicator = chart.indicators[i];
          technicalIndicatorsRenderer = stateProperties.technicalIndicatorRenderer[i];
          setIndicatorType(indicator, technicalIndicatorsRenderer);
          textCollection.add(technicalIndicatorsRenderer.indicatorType);
        }
        map = <String, int>{};
        for (final str in textCollection) {
          map[str] = !map.containsKey(str) ? (1) : (map[str]! + 1);
        }
      }

      final List<String> indicatorTextCollection = <String>[];
      for (int i = 0; i < chart.indicators.length; i++) {
        indicator = chart.indicators[i];
        technicalIndicatorsRenderer = stateProperties.technicalIndicatorRenderer[i];
        technicalIndicatorsRenderer.dataPoints = <CartesianChartPoint<dynamic>>[];
        technicalIndicatorsRenderer.index = i;
        if (!chart.legend.isVisible!) {
          final int count = indicatorTextCollection.contains(technicalIndicatorsRenderer.indicatorType)
              ? getIndicatorId(indicatorTextCollection, technicalIndicatorsRenderer.indicatorType)
              : 0;
          indicatorTextCollection.add(technicalIndicatorsRenderer.indicatorType);
          technicalIndicatorsRenderer.name = indicator.name ??
              (technicalIndicatorsRenderer.indicatorType +
                  (map[technicalIndicatorsRenderer.indicatorType] == 1 ? '' : ' $count'));
        }
        if (indicator != null &&
            indicator.isVisible == true &&
            (indicator.dataSource != null || indicator.seriesName != null)) {
          if (indicator.dataSource != null && indicator.dataSource.isNotEmpty == true) {
            existField = technicalIndicatorsRenderer.indicatorType == 'SMA' ||
                technicalIndicatorsRenderer.indicatorType == 'TMA' ||
                technicalIndicatorsRenderer.indicatorType == 'EMA';
            final String valueField = existField ? _getFieldType(indicator).toLowerCase() : '';
            CartesianChartPoint<dynamic> currentPoint;
            for (int pointIndex = 0; pointIndex < indicator.dataSource.length; pointIndex++) {
              if (indicator.xValueMapper != null) {
                final dynamic xVal = indicator.xValueMapper(pointIndex);
                num? highValue;
                num? lowValue;
                num? openValue;
                num? closeValue;
                num? volumeValue;
                technicalIndicatorsRenderer.dataPoints!.add(CartesianChartPoint<dynamic>(xVal));
                currentPoint =
                    technicalIndicatorsRenderer.dataPoints![technicalIndicatorsRenderer.dataPoints!.length - 1];
                if (indicator.highValueMapper != null) {
                  highValue = indicator.highValueMapper(pointIndex);
                  technicalIndicatorsRenderer.dataPoints![technicalIndicatorsRenderer.dataPoints!.length - 1].high =
                      highValue;
                }
                if (indicator.lowValueMapper != null) {
                  lowValue = indicator.lowValueMapper(pointIndex);
                  technicalIndicatorsRenderer.dataPoints![technicalIndicatorsRenderer.dataPoints!.length - 1].low =
                      lowValue;
                }

                if (currentPoint.high != null && currentPoint.low != null) {
                  final num high = currentPoint.high;
                  final num low = currentPoint.low;
                  currentPoint.high = math.max<num>(high, low);
                  currentPoint.low = math.min<num>(high, low);
                }
                if (indicator.openValueMapper != null) {
                  openValue = indicator.openValueMapper(pointIndex);
                  technicalIndicatorsRenderer.dataPoints![technicalIndicatorsRenderer.dataPoints!.length - 1].open =
                      openValue;
                }
                if (indicator.closeValueMapper != null) {
                  closeValue = indicator.closeValueMapper(pointIndex);
                  technicalIndicatorsRenderer.dataPoints![technicalIndicatorsRenderer.dataPoints!.length - 1].close =
                      closeValue;
                }
                if (indicator is AccumulationDistributionIndicator && indicator.volumeValueMapper != null) {
                  volumeValue = indicator.volumeValueMapper!(pointIndex);
                  technicalIndicatorsRenderer.dataPoints![technicalIndicatorsRenderer.dataPoints!.length - 1].volume =
                      volumeValue;
                }

                if ((closeValue == null && (!existField || valueField == 'close')) ||
                    (highValue == null &&
                        (valueField == 'high' ||
                            technicalIndicatorsRenderer.indicatorType == 'AD' ||
                            technicalIndicatorsRenderer.indicatorType == 'ATR' ||
                            technicalIndicatorsRenderer.indicatorType == 'RSI' ||
                            technicalIndicatorsRenderer.indicatorType == 'Stochastic')) ||
                    (lowValue == null &&
                        (valueField == 'low' ||
                            technicalIndicatorsRenderer.indicatorType == 'AD' ||
                            technicalIndicatorsRenderer.indicatorType == 'ATR' ||
                            technicalIndicatorsRenderer.indicatorType == 'RSI' ||
                            technicalIndicatorsRenderer.indicatorType == 'Stochastic')) ||
                    (openValue == null && valueField == 'open') ||
                    (volumeValue == null && technicalIndicatorsRenderer.indicatorType == 'AD')) {
                  technicalIndicatorsRenderer.dataPoints!.removeAt(technicalIndicatorsRenderer.dataPoints!.length - 1);
                }
              }
            }
          } else if (indicator.seriesName != null) {
            CartesianSeriesRenderer? seriesRenderer;

            for (int i = 0; i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) {
              if (indicator.seriesName ==
                  SeriesHelper.getSeriesRendererDetails(stateProperties.chartSeries.visibleSeriesRenderers[i])
                      .series
                      .name) {
                seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[i];
                break;
              }
            }
            final SeriesRendererDetails? seriesRendererDetails =
                seriesRenderer != null ? SeriesHelper.getSeriesRendererDetails(seriesRenderer) : null;
            technicalIndicatorsRenderer.dataPoints = (seriesRendererDetails != null &&
                    (seriesRendererDetails.seriesType == 'hiloopenclose' ||
                        seriesRendererDetails.seriesType == 'candle' ||
                        seriesRendererDetails.seriesType == 'boxandwhisker'))
                ? seriesRendererDetails.dataPoints
                : null;
          }
          if (technicalIndicatorsRenderer.dataPoints != null && technicalIndicatorsRenderer.dataPoints!.isNotEmpty) {
            technicalIndicatorsRenderer.initDataSource(indicator, technicalIndicatorsRenderer, chart);
            if (technicalIndicatorsRenderer.renderPoints.isNotEmpty) {
              stateProperties.chartSeries.visibleSeriesRenderers
                  .addAll(technicalIndicatorsRenderer.targetSeriesRenderers);
            }
          }
        }
      }
    }
  }

  String _getFieldType(TechnicalIndicators<dynamic, dynamic> indicator) {
    String valueField = '';
    if (indicator is EmaIndicator) {
      valueField = indicator.valueField;
    } else if (indicator is TmaIndicator) {
      valueField = indicator.valueField;
    } else if (indicator is SmaIndicator) {
      valueField = indicator.valueField;
    }
    return valueField;
  }

  int getIndicatorId(List<String> list, String str) {
    int count = 0;
    for (int i = 0; i < list.length; i++) {
      if (list[i] == str) {
        count++;
      }
    }
    return count;
  }

  void setIndicatorType(
      TechnicalIndicators<dynamic, dynamic> indicator, TechnicalIndicatorsRenderer technicalIndicatorsRenderer,) {
    if (indicator is AtrIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'ATR';
    } else if (indicator is AccumulationDistributionIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'AD';
    } else if (indicator is BollingerBandIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'Bollinger';
    } else if (indicator is EmaIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'EMA';
    } else if (indicator is MacdIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'MACD';
    } else if (indicator is MomentumIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'Momentum';
    } else if (indicator is RsiIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'RSI';
    } else if (indicator is SmaIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'SMA';
    } else if (indicator is StochasticIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'Stochastic';
    } else if (indicator is TmaIndicator) {
      technicalIndicatorsRenderer.indicatorType = 'TMA';
    }
  }

  void _sortDateTimeCategoryDetails(SeriesRendererDetails seriesRendererDetails) {
    final DateTimeCategoryAxisDetails axisDetails = seriesRendererDetails.xAxisDetails as DateTimeCategoryAxisDetails;
    seriesRendererDetails.dataPoints.sort((point1, point2) {
      return point2.x.isAfter(point1.x) == true ? -1 : 1;
    });
    axisDetails.labels.sort((first, second) {
      return int.parse(first) < int.parse(second) ? -1 : 1;
    });
    seriesRendererDetails.xValues?.sort();
    for (final CartesianChartPoint<dynamic> point in seriesRendererDetails.dataPoints) {
      point.xValue = axisDetails.labels.indexOf(point.x.microsecondsSinceEpoch.toString());
    }
  }
}
